
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Text.RegularExpressions;

using Swaf;
using Swaf.BizObj;
using Swaf.BizRule;
using Swaf.Container;
using Swaf.Security;

namespace Syrinx
{
	public class BizObjTreeBizRules : Syrinx.BaseBizRules
	{
		public static char KeyNameSeperator = '\xd';

		public void increateNodeViewCount(IApplication app, IRuleContext context, Swaf.BGuid.Guid id)
		{
			if (app.sessionData["NVCount"] == null)
				app.sessionData["NVCount"] = new FlexiMap();
			FlexiMap m = (FlexiMap)app.sessionData["NVCount"];
			string ids = id.ToString();
			if (m.get(ids) == null)
			{
				m.put(ids, 1, "expire in 5 seconds");
				IResults rc = context.RuleGroup.execute(context, "getNodeCountInfo", id, false);
				context.put("id", id);
				app.appData.put("DA.Syrinx.IncrementNodeViewCount", context);
			}
		}

		public void incrementParentNodeCounts(IApplication app, IRuleContext context, Swaf.BGuid.Guid id, DateTime updated)
		{
			context.put("id", id);
			IBizObj treeNode = app.appData.get("DA.Syrinx.GetTreeNode", context) as IBizObj;
			IBizObjList parents = context.RuleGroup.execute(context, "getBizObjTreeParents", treeNode.get("treeName"), id, "ASC", false).CallResults as IBizObjList;
			int depth = (int)treeNode.get("depth");
			for (int p = 0; p < 4 && p < depth; ++p)
			{
				IResults rc = context.RuleGroup.execute(context, "getNodeCountInfo", parents[p].get("id"), false);
				int level = depth - p;
				if (level > 4)
					level = 4;
				context.put("id", parents[p].get("id"));
				context.put("level", level);
				context.put("lastChildUpdated", updated);
				context.put("lastChildUpdatedByUserId", CurrentUser.get("id"));
				app.appData.put("DA.Syrinx.IncrementNodeChildCount", context);
			}
		}

		public void updateParentNodesLastUpdated(IApplication app, IRuleContext context, Swaf.BGuid.Guid id, DateTime updated, Swaf.BGuid.Guid userId)
		{
			context.put("id", id);
			IBizObj treeNode = app.appData.get("DA.Syrinx.GetTreeNode", context) as IBizObj;
			IBizObjList parents = context.RuleGroup.execute(context, "getBizObjTreeParents", treeNode.get("treeName"), id, "ASC", false).CallResults as IBizObjList;
			int depth = (int)treeNode.get("depth");
			for (int p = 0; p < 4 && p < depth; ++p)
			{
				IResults rc = context.RuleGroup.execute(context, "getNodeCountInfo", parents[p].get("id"), false);
				int level = depth - p;
				if (level > 4)
					level = 4;
				context.put("id", parents[p].get("id"));
				context.put("level", level);
				context.put("lastChildUpdated", updated);
				context.put("lastChildUpdatedByUserId", CurrentUser.get("id"));
				app.appData.put("DA.Syrinx.UpdateNodeChildCountLastChildUpdated", context);
			}
		}

		public IBizObj decrecrementParentNodeCounts(IApplication app, IRuleContext context, Swaf.BGuid.Guid id)
		{
			context.put("id", id);
			app.appData.put("DA.Syrinx.DeleteNodeChildCount", context);
			IBizObj treeNode = app.appData.get("DA.Syrinx.GetTreeNode", context) as IBizObj;
			IBizObjList parents = context.RuleGroup.execute(context, "getBizObjTreeParents", treeNode.get("treeName"), id, "ASC", false).CallResults as IBizObjList;
			int depth = (int)treeNode.get("depth");
			for (int p = 0; p < 4 && p < depth; ++p)
			{
				IResults rc = context.RuleGroup.execute(context, "getNodeCountInfo", parents[p].get("id"),false);
				context.put("id", parents[p].get("id"));
				int level = depth - p;
				if (level > 4)
					level = 4;
				context.put("level", level);
				app.appData.put("DA.Syrinx.DecrementNodeChildCount", context);
			}
			return treeNode;
		}

		public void refreshLatestUpdatedForParents(IApplication app, IRuleContext context, Swaf.BGuid.Guid parentId)
		{
			context.put("id", parentId);
			IBizObj treeNode = app.appData.get("DA.Syrinx.GetTreeNode", context) as IBizObj;
			IBizObjList parents = context.RuleGroup.execute(context, "getBizObjTreeParents", treeNode.get("treeName"), parentId, "ASC", false).CallResults as IBizObjList;
			int depth = (int)treeNode.get("depth");
			if (depth < 4)
				context.RuleGroup.execute(context, "getNodeCountInfo", parentId, true);
			foreach (IBizObj parent in parents)
				context.RuleGroup.execute(context, "getNodeCountInfo", parent.get("id"), true);
		}

		public IBizObj getNodeCountInfo(IApplication app, IRuleContext context, Swaf.BGuid.Guid id, bool forceUpdate)
		{
			context.put("id", id);
			IBizObj NodeCount = app.appData.get("DA.Syrinx.GetNodeCountInfo", context) as IBizObj;
			if (NodeCount == null || forceUpdate)
			{
				if (NodeCount != null)
					app.appData.put("DA.Syrinx.DeleteNodeChildCount", context);

				IBizObj treeNode = app.appData.get("DA.Syrinx.GetTreeNode", context) as IBizObj;
				IBizObj counts = app.bizObjMgr.create("ObjectTreeNodeCount");
				counts.put("objectId", treeNode.get("id"));
				counts.put("keyName", treeNode.get("keyName"));
				counts.put("treeName", treeNode.get("treeName"));

				if ((int)treeNode.get("depth") <= 4)
				{
					IBizObjList children = context.RuleGroup.execute(context, "getBizObjTreeChildren", counts.get("objectId"), true).CallResults as IBizObjList;
					if (children != null && children.numElements > 0)
					{
						for (int pos = 0; pos < children.numElements; ++pos)
						{
							IBizObj child = children[pos];
							int level = (int)child.get("depth") - (int)treeNode.get("depth");
							if (level > 4)
								level = 4;
							string lvlName = "totalL" + level;
							counts.put(lvlName, ((int)counts.get(lvlName)) + 1);
						}
					}
				}

				app.appData.put("DA.Syrinx.AddNodeChildCount", counts);
			}
			return NodeCount;
		}

		/// <summary>
		/// TODO: Use some kind of db lock on top root element to avoid other bizrule calls from 
		/// trying to modify that tree while this is working.  Right now, there is a big risk that two
		/// people modifying the same tree will cause problems
		/// Will insert a bizobj into a tree of bizobjs.
		/// </summary>
		/// <param name="app"></param>
		/// <param name="context"></param>
		/// <param name="id"></param>
		/// <param name="parentId"></param>
		/// <param name="treeName"></param>
		public void insertBizObjIntoTree(IApplication app, IRuleContext context, IBizObj node, bool uniqueInTree)
		{
			try
			{
				string keyName = (string)node.get("name");
				context.put("id", uniqueInTree ? node.get("id") : new Swaf.BGuid.Guid());
				context.put("parentId", node.get("parentId")==null?context.get("id"):node.get("parentId"));
				context.put("name", keyName);
				context.put("objectId", node.get("id"));
				context.put("treeName", node.get("treeName"));

				IBizObj parent = app.appData.get("DA.Syrinx.GetParentNode", context) as IBizObj;

				context.put("parentHostId", node.get("parentId") == null ? null : node.get("parentId"));
				IBizObj lastChild = app.appData.get("DA.Syrinx.GetChildLastNode", context) as IBizObj;

				int pos = lastChild==null?-1:(int)lastChild.get("positionInParent");
				pos++;
				context.put("positionInParent", pos);
				if (parent != null)
				{
					keyName = string.Format("{0}{1}{2}", parent.get("keyName"), KeyNameSeperator, keyName);
					context.put("rootId", parent.get("rootId"));
					context.put("positionName", string.Format("{0}{1}{2:0000000}", parent.get("positionName"),KeyNameSeperator, context.get("positionInParent")));
				}
				else
				{
					context.put("rootId", context.get("id"));
					context.put("positionName", context.get("positionInParent").ToString());
				}

				string[] depth = keyName.Split(KeyNameSeperator);
				node.put("keyName", keyName);
				context.put("keyName", keyName);
				context.put("depth", depth.Length - 1);
				if (node.get("parentId") != null)
				{
					object orgid = context.get("id");
					context.put("id", node.get("parentId"));
					IBizObjList parentPath = (IBizObjList)app.appData.get("DA.Syrinx.GetTreeParentPath", context);
					context.put("id", orgid);
					foreach (IBizObj pp in parentPath)
					{
						context.put("parentHostId", pp.get("parentHostId"));
						app.appData.put("DA.Syrinx.InsertTreeRelationship", context);
					}
				}

				context.put("parentHostId", node.get("id"));
				app.appData.get("DA.Syrinx.InsertTreeRelationship", context);
			}
			catch (Exception e)
			{
				throw new SyrinxException("Error executing insertBizObjIntoTree.", e);
			}
		}

		public void updateBizObjTreeNode(IApplication app, IRuleContext context, IBizObj node, bool checkForReparent)
		{
			DateTime now = DateTime.Now.ToUniversalTime();
			node.put("counts.objectId", node.get("id"));
			node.put("counts.lastChildUpdated", now);
			node.put("counts.lastChildUpdatedBy.id", CurrentUser.get("id"));
			if (node.isNew)
			{
				context.RuleGroup.execute(context, "insertBizObjIntoTree", node, true);
				node.put("counts.treeName", node.get("treeName"));
				node.put("counts.keyName", node.get("keyName"));
				app.appData.put("DA.Syrinx.AddNodeChildCount", node.get("counts"));
				context.RuleGroup.execute(context, "incrementParentNodeCounts", node.get("id"), now);
			}
			else
			{
				app.appData.put("DA.Syrinx.UpdateNodeChildCount", node.get("counts"));
				if (checkForReparent && node.isDirty("parentId"))
					app.ruleMgr.execute(context, "moveBizObjInTree", node, true);
				else if ((node.containsField("basetitle") && node.isDirty("baseTitle")) || node.isDirty("name"))
					app.ruleMgr.execute(context, "renameBizObjInTree", node, "");
				//Swaf.BGuid.Guid id, DateTime updated, Swaf.BGuid.Guid userId
				context.RuleGroup.execute(context, "updateParentNodesLastUpdated", (Swaf.BGuid.Guid)node.get("id"), now, (Swaf.BGuid.Guid)CurrentUser.get("id"));
			}
		}

		public void removeBizObjFromTree(IApplication app, IRuleContext context, Swaf.BGuid.Guid id, bool deep)
		{
			try
			{
				context.put("id", id);
				IBizObj treeNode = !deep ? app.appData.get("DA.Syrinx.GetTreeNode", context) as IBizObj : null;
				IBizObjList children = getBizObjTreeChildren(app, context, id, true) as IBizObjList;
				app.appData.put("DA.Syrinx.RemoveFromTree", context);

				foreach (IBizObj child in children)
				{
					context.put("id", child.get("id"));
					app.appData.put("DA.Syrinx.RemoveFromTree", context);
				}

				if (!deep)
					foreach (IBizObj child in children)
						insertBizObjIntoTree(app, context, child, true);
			}
			catch (SyrinxException e2)
			{
				throw;
			}
			catch (Exception e)
			{
				throw new SyrinxException("Error executing removeBizObjFromTree.", e);
			}
		}

		public void moveBizObjInTree(IApplication app, IRuleContext context, IBizObj node, bool isUnique)
		{
			string treeName = (string)node.get("treeName");
			Swaf.BGuid.Guid id = (Swaf.BGuid.Guid)node.get("id");
			Swaf.BGuid.Guid parentId = (Swaf.BGuid.Guid)node.get("parentId");
			IBizObjList children = getBizObjTreeChildren(app, context, id, true) as IBizObjList;

			removeBizObjFromTree(app, context, id, true);
			insertBizObjIntoTree(app, context, node, isUnique);
			foreach(IBizObj child in children)
				insertBizObjIntoTree(app, context, child, isUnique);
		}

		public IBizObjList getBizObjTreeParents(IApplication app, IRuleContext context, string treeName, Swaf.BGuid.Guid id, string dir, bool stripLeading)
		{
			context.put("id", id);
			context.put("dir", dir);
            context.put("stripLeading", stripLeading);
			IBizObjList parents = (IBizObjList)app.appData.get("DA.Syrinx.GetTreeParentPath", context);

			return parents;
		}

		public IBizObjList getBizObjTreeChildren(IApplication app, IRuleContext context, Swaf.BGuid.Guid id, bool deep)
		{
			if (id == null)
				throw new Exception("invalid to pass null as id");
			try
			{
				FlexiMap m = new FlexiMap();
				m.put("parentId", id);
				//m.put("parentHostId", id);

				object list = app.appData.get((deep) ? "DA.Syrinx.GetSubTree" : "DA.Syrinx.GetTreeNodeChildren", m);
				return (IBizObjList)list;
			}
			catch (Exception e)
			{
				throw new SyrinxException("Error executing getChildCompanies bizrule", e);
			}
		}

		public IBizObj getBizObjFromKeyName(IApplication app, IRuleContext context, string treeName, bool dummy, string keyName)
		{
			context.put("keyName", keyName);
			context.put("treeName", treeName);
			IBizObj bizObj = app.appData.get("DA.Syrinx.GetBizObjFromKeyName", context) as IBizObj;
			return bizObj;
		}
		/// <summary>
		/// this method should be called when renaming a bizobj that is using the 
		/// relationship tree (bizobjtree). failure to call this method will result in an 
		/// odd behavior as object that has children or is presented as a tree structure 
		/// relies on the keynames for navigating the heirarchy.
		/// </summary>
		/// <param name="app"></param>
		/// <param name="context"></param> 
		/// <param name="treeName">name of the tree. current version doesnt use this value</param>
		/// <param name="id">this is the object bguid id</param>
		/// <param name="newTreeNodeName">the new name</param>
		/// <param name="pattern">this should be an empty string otherwise pass in here the old name of the bizobj</param>
		public void renameBizObjInTree(IApplication app, IRuleContext context, IBizObj node, string pattern)
		{
			try
			{
				string newTreeNodeName = (string)node.get("name");
				IBizObj treeNode = app.appData.get("DA.Syrinx.GetTreeNode", node) as IBizObj;
				if (treeNode != null)
				{
					string keyName = (string)treeNode.get("keyName");
					string name = (string)treeNode.get("name");
					string newPrefix = newTreeNodeName;
					if (name.Length != keyName.Length)
					{
						string origionalPrefix = keyName.Substring(0, keyName.Length - (name.Length + 1));
						newPrefix = origionalPrefix + KeyNameSeperator + newTreeNodeName;
					}

					treeNode.put("name", newTreeNodeName);
					treeNode.put("keyName", newPrefix);
					app.appData.put("DA.Syrinx.RenameNodeByObjectId", treeNode);
					IBizObjList children = getBizObjTreeChildren(app, context, (Swaf.BGuid.Guid)node.get("id"), true);
					foreach (IBizObj child in children)
					{
						string childKeyName = (string)child.get("keyName");
						child.put("keyName", newPrefix + childKeyName.Substring(keyName.Length));
						app.appData.put("DA.Syrinx.RenameNodeByObjectId", child);
					}
				}



				//FlexiMap m = new FlexiMap();
				//m["id"] = id;
				//m["keyName"] = newTreeNodeName;
				//m["treeName"] = treeName;
				//IBizObj treeNode = app.appData.get("DA.Syrinx.GetTreeNode", m) as IBizObj;
				//if (treeNode != null)
				//{
				//    m["objectId"] = id;
				//    m["name"] = (pattern.Length > 0) ? treeNode.get("name").ToString() : newTreeNodeName;
				//    pattern = (pattern.Length > 0) ? pattern : treeNode.get("name").ToString();
				//    if (string.Compare(newTreeNodeName, pattern, false) == 0)
				//        return;
				//    //TODO: rewrite code [bug when there is no input provided or input is zero lenght string]
				//    Regex rgx = new Regex(pattern);
				//    string input = treeNode.get("keyName").ToString();
				//    string output = rgx.Replace(input, newTreeNodeName);
				//    m["keyName"] = output;
				//    app.appData.put("DA.Syrinx.RenameNodeByObjectId", m);
				//    IBizObjList subTree = app.appData.get("DA.Syrinx.GetChildNodes", m) as IBizObjList;
				//    if (subTree.numElements > 0)
				//        foreach(IBizObj obj in subTree)
				//            renameBizObjInTree(app, context, treeName, obj.get("id") as Swaf.BGuid.Guid, newTreeNodeName, pattern);
				//}
			}
			catch (Exception e)
			{
				throw new SyrinxException("Error executing renameBizObjInTree bizrule.", e);
			}
		}

		public void fixupTreeKeyNames(IApplication app, IRuleContext context)
		{
			IBizObjList allTreeNodes = app.appData.get("DA.Syrinx.GetAllTreeNodes", context) as IBizObjList;
			if(allTreeNodes != null)
				foreach (IBizObj node in allTreeNodes)
				{
                    bool tryChange = true;
					if (node.get("parentId") != null && ((IComparable)node.get("parentId")).CompareTo(node.get("id")) != 0)
					{
						context.put("id", node.get("parentId"));
						IBizObj parent = app.appData.get("DA.Syrinx.GetTreeNode", context) as IBizObj;
                        if (parent == null)
                        {
                            if (((Swaf.BGuid.Guid)node.get("id")).CompareTo(node.get("rootId")) == 0)
                                app.Log.println(Swaf.Logging.LoggingLevels.High, "Wrong parent id for article id='{0}' title='{1}' with parent id='{2}', parentId should be the same as id", node.get("objectId"), node.get("name"), node.get("parentId"));
                            else
                            {
                                tryChange = false;
                                app.Log.println(Swaf.Logging.LoggingLevels.High, "Missing parent for article id='{0}' title='{1}' with parent id='{2}'", node.get("objectId"), node.get("name"), node.get("parentId"));

                            }
                        }
                        if(tryChange)
                        {
                            if (parent != null)
                            {
                                string keyName = (string)parent.get("keyName") + KeyNameSeperator + (string)node.get("name");
                                string origionalKeyName = (string)node.get("keyName");
                                if (string.Compare(keyName, origionalKeyName) != 0)
                                {
                                    string bad = "badparent";
                                }

                                string[] depth = keyName.Split(KeyNameSeperator);
                                node.put("keyName", keyName);
                                node.put("depth", depth.Length - 1);
                            }
                            else
                                node.put("parentId", node.get("id"));
                            app.appData.put("DA.Syrinx.UpdateTreeNodeKeyNameDepth", node);
                        }
					}
				}
		}
	}
}
